<?php
/**
 * Idea Framework (http://www.ideaphp.net/)
 *
 * @author    Huan Song <ideaphp@gmail.com>
 * @link      http://www.ideaphp.net/
 * @copyright Copyright (c) 2011-2013 http://www.ideaphp.net/ All rights reserved.
 * @license   http://www.apache.org/licenses/LICENSE-2.0
 */

namespace idea\saf\dl;

use idea\ap\Singleton;
use idea\db\TableSchema;
use idea\saf\DbProxy;

/**
 * Dao abstract class file
 * Dao层的父类，封装单表基本SQL操作
 * @author 宋欢 <ideaphp@gmail.com>
 * @version $Id: Dao.php 1 2013-04-05 19:53:06Z huan.song $
 * @package idea.saf.dl
 * @since 1.0
 */
abstract class Dao
{
    /**
     * @var instance of idea\db\TableSchema
     */
    protected $_tableSchema = null;

    /**
     * @var string 表名
     */
    protected $_tableName = null;

    /**
     * @var instance of idea\saf\DbProxy
     */
    protected $_dbProxy = null;

    /**
     * @var string 被引用的主键名，可以在SQL中直接使用
     */
    protected $_quotePrimaryKey = '';

    /**
     * 构造方法：初始化表名和数据库操作类
     * @param string $tableName
     * @param idea\saf\DbProxy $dbProxy
     */
    public function __construct($tableName, DbProxy $dbProxy)
    {
        $this->_tableName = $tableName;
        $this->_dbProxy = $dbProxy;
    }

    /**
     * 通过主键，获取某个列的值
     * @param string $columnName
     * @param integer $value
     * @return mixed
     */
    public function getByPk($columnName, $value)
    {
        $attributes = array(
            $this->getTableSchema()->primaryKey => $value
        );

        return $this->getByAttributes($columnName, $attributes);
    }

    /**
     * 通过多个字段名和值，获取主键的值，字段之间用简单的AND连接
     * @param array $attributes
     * @return mixed
     */
    public function getPkByAttributes(array $attributes = array())
    {
        $condition = $this->getCommandBuilder()->createAndCondition(array_keys($attributes));
        return $this->getPkByCondition($condition, $attributes);
    }

    /**
     * 通过多个字段名和值，获取某个列的值，字段之间用简单的AND连接
     * @param string $columnName
     * @param array $attributes
     * @return mixed
     */
    public function getByAttributes($columnName, array $attributes = array())
    {
        $condition = $this->getCommandBuilder()->createAndCondition(array_keys($attributes));
        return $this->getByCondition($columnName, $condition, $attributes);
    }

    /**
     * 通过多个字段名和值，查询多条记录，字段之间用简单的AND连接
     * @param array $attributes
     * @param string $order
     * @param integer $limit
     * @param integer $offset
     * @return array
     */
    public function findAllByAttributes(array $attributes = array(), $order = '', $limit = 0, $offset = 0)
    {
        $condition = $this->getCommandBuilder()->createAndCondition(array_keys($attributes));
        return $this->findAllByCondition($condition, $attributes, $order, $limit, $offset);
    }

    /**
     * 通过多个字段名和值，统计记录数，字段之间用简单的AND连接
     * @param array $attributes
     * @return integer
     */
    public function countByAttributes(array $attributes = array())
    {
        $condition = $this->getCommandBuilder()->createAndCondition(array_keys($attributes));
        return $this->countByCondition($condition, $attributes);
    }

    /**
     * 通过多个字段名和值，查询一条记录，字段之间用简单的AND连接
     * @param array $attributes
     * @return array
     */
    public function findByAttributes(array $attributes = array())
    {
        $condition = $this->getCommandBuilder()->createAndCondition(array_keys($attributes));
        return $this->findByCondition($condition, $attributes);
    }

    /**
     * 通过条件，获取主键的值
     * @param string $condition
     * @param mixed $params
     * @return mixed
     */
    public function getPkByCondition($condition, $params = null)
    {
        return $this->getByCondition($this->getTableSchema()->primaryKey, $condition, $params);
    }

    /**
     * 通过条件，获取某个列的值
     * @param string $columnName
     * @param string $condition
     * @param mixed $params
     * @return mixed
     */
    public function getByCondition($columnName, $condition, $params = null)
    {
        $sql = $this->getCommandBuilder()->createFind($this->getTableSchema()->name, array($columnName), $condition);
        return $this->getDbProxy()->fetchColumn($sql, $params);
    }

    /**
     * 获取表中所有的记录
     * @return array
     */
    public function findAll($order = '', $limit = 0, $offset = 0)
    {
        return $this->findAllByCondition(1, null, $order, $limit, $offset);
    }

    /**
     * 通过条件，查询多条记录
     * @param string $condition
     * @param mixed $params
     * @param string $order
     * @param integer $limit
     * @param integer $offset
     * @return array
     */
    public function findAllByCondition($condition, $params = null, $order = '', $limit = 0, $offset = 0)
    {
        $sql = $this->getCommandBuilder()->createFind($this->getTableSchema()->name, $this->getTableSchema()->columnNames, $condition, $order, $limit, $offset);
        return $this->getDbProxy()->fetchAll($sql, $params);
    }

    /**
     * 通过条件，统计记录数
     * @param string $condition
     * @param mixed $params
     * @return integer
     */
    public function countByCondition($condition, $params = null)
    {
        $sql = $this->getCommandBuilder()->createCount($this->getTableSchema()->name, $condition);
        return $this->getDbProxy()->fetchColumn($sql, $params);
    }

    /**
     * 通过条件，查询一条记录
     * @param string $condition
     * @param mixed $params
     * @return array
     */
    public function findByCondition($condition, $params = null)
    {
        $sql = $this->getCommandBuilder()->createFind($this->getTableSchema()->name, $this->getTableSchema()->columnNames, $condition);
        return $this->getDbProxy()->fetch($sql, $params);
    }

    /**
     * 通过主键，查询一条记录
     * @param integer $value
     * @return array
     */
    public function findByPk($value)
    {
        $condition = $this->getQuotePrimaryKey() . ' = ' . CommandBuilder::PLACE_HOLDERS;
        $sql = $this->getCommandBuilder()->createFind($this->getTableSchema()->name, $this->getTableSchema()->columnNames, $condition);
        return $this->getDbProxy()->fetch($sql, $value);
    }

    /**
     * 插入一条新记录
     * @param array $attributes
     * @param boolean $filter
     * @param boolean $ignore
     * @return integer
     */
    public function insert(array $attributes = array(), $ignore = false)
    {
        $sql = $this->getCommandBuilder()->createInsert($this->getTableSchema()->name, array_keys($attributes), $ignore);
        if ($this->getDbProxy()->query($sql, $attributes)) {
            return $this->getDbProxy()->getLastInsertId();
        }

        return false;
    }

    /**
     * 通过主键，更新一条记录
     * @param integer $value
     * @param array $attributes
     * @return integer
     */
    public function updateByPk($value, array $attributes = array())
    {
        $condition = $this->getQuotePrimaryKey() . ' = ' . CommandBuilder::PLACE_HOLDERS;
        $sql = $this->getCommandBuilder()->createUpdate($this->getTableSchema()->name, array_keys($attributes), $condition);
        array_push($attributes, $value);
        if ($this->getDbProxy()->query($sql, $attributes)) {
            return $this->getDbProxy()->getRowCount();
        }

        return false;
    }

    /**
     * 通过主键，更新多条记录
     * @param string $value
     * @param array $attributes
     * @return integer
     */
    public function batchUpdateByPk($value, array $attributes = array())
    {
        $condition = $this->getQuotePrimaryKey() . ' IN (' . $value . ')';
        $sql = $this->getCommandBuilder()->createUpdate($this->getTableSchema()->name, array_keys($attributes), $condition);
        if ($this->getDbProxy()->query($sql, $attributes)) {
            return $this->getDbProxy()->getRowCount();
        }

        return false;
    }

    /**
     * 通过主键，删除一条记录
     * @param integer $value
     * @return integer
     */
    public function deleteByPk($value)
    {
        $condition = $this->getQuotePrimaryKey() . ' = ' . CommandBuilder::PLACE_HOLDERS;
        $sql = $this->getCommandBuilder()->createDelete($this->getTableSchema()->name, $condition);
        if ($this->getDbProxy()->query($sql, $value)) {
            return $this->getDbProxy()->getRowCount();
        }

        return false;
    }

    /**
     * 通过主键，删除多条记录
     * @param string $value
     * @return integer
     */
    public function batchDeleteByPk($value)
    {
        $condition = $this->getQuotePrimaryKey() . ' IN (' . $value . ')';
        $sql = $this->getCommandBuilder()->createDelete($this->getTableSchema()->name, $condition);
        if ($this->getDbProxy()->query($sql, $value)) {
            return $this->getDbProxy()->getRowCount();
        }

        return false;
    }

    /**
     * 添加一条记录，如果记录存在则更新
     * @param array $attributes
     * @return integer
     */
    public function replace(array $attributes = array())
    {
        $sql = $this->getCommandBuilder()->createReplace($this->getTableSchema()->name, array_keys($attributes));
        if ($this->getDbProxy()->query($sql, $attributes)) {
            return $this->getDbProxy()->getRowCount();
        }

        return false;
    }

    /**
     * 通过过滤数组，只保留表中含有的字段名
     * 如果是为INSERT、UPDATE服务，可以设置$autoIncrement为true，会清理掉自增字段
     * @param array $attributes
     * @return void
     */
    public function filterAttributes(array &$attributes = array(), $autoIncrement = true)
    {
        $tableSchema = $this->getTableSchema();
        foreach ($attributes as $columnName => $value) {
            if (!$tableSchema->hasColumn($columnName)) {
                unset($attributes[$columnName]);
            }
        }

        if ($autoIncrement && $tableSchema->autoIncrement !== null && isset($attributes[$tableSchema->autoIncrement])) {
            unset($attributes[$tableSchema->autoIncrement]);
        }
    }

    /**
     * 获取DaoProfile类
     * @param integer $idValue
     * @return idea\saf\DaoProfile
     */
    public function getProfile($idValue)
    {
        $tableName = $this->getTableName() . '_profile';
        return Profile::getInstance($this->getTableName(), $idValue, $this->getDbProxy());
    }

    /**
     * 获取表名
     * @return string
     */
    public function getTableName()
    {
        return $this->_tableName;
    }

    /**
     * 引用一个表的主键，被引用的主键名可以放在SQL语句中执行
     * @return string
     */
    public function getQuotePrimaryKey()
    {
        if ($this->_quotePrimaryKey === '') {
            $this->_quotePrimaryKey = $this->getCommandBuilder()->quoteColumnName($this->getTableSchema()->primaryKey);
        }

        return $this->_quotePrimaryKey;
    }

    /**
     * 获取数据库操作类
     * @return idea\saf\DbProxy
     */
    public function getDbProxy()
    {
        return $this->_dbProxy;
    }

    /**
     * 获取创建简单的执行命令类
     * @return idea\saf\dl\CommandBuilder
     */
    public function getCommandBuilder()
    {
        return Singleton::getInstance('idea\saf\dl\CommandBuilder');
    }

    /**
     * 通过表的实体类，获取表的概要描述，包括：表名、主键、自增字段、字段、默认值
     * 应该根据不同的数据库类型创建对应的TableSchema类
     * 这里只用到MySQL数据库，暂时不做对应多数据库类型
     * $dbType = $this->getDriver(false)->getDbType();
     * @return idea\db\TableSchema
     */
    public function getTableSchema()
    {
        if ($this->_tableSchema !== null) {
            return $this->_tableSchema;
        }

        $className = 'idea\db\TableSchema::' . $this->getTableName();
        if (($instance = Singleton::get($className)) !== null) {
            return $this->_tableSchema = $instance;
        }

        $ref = EntityBuilder::getInstance($this->getDbProxy())->getRefClass($this->getTableName());
        $attributes = $ref->getDefaultProperties();
        $columnNames = array_keys($attributes);
        foreach ($attributes as $key => $value) {
            if ($value === null) {
                unset($attributes[$key]);
            }
        }

        $tableSchema = new TableSchema();
        $tableSchema->name = $ref->hasConstant('TABLE_NAME') ? $ref->getConstant('TABLE_NAME') : $ref->getShortName();
        $tableSchema->columnNames = $columnNames;
        $tableSchema->primaryKey = $ref->hasConstant('PRIMARY_KEY') ? $ref->getConstant('PRIMARY_KEY') : array_shift($columnNames);
        $tableSchema->autoIncrement = $ref->hasConstant('AUTO_INCREMENT') ? $ref->getConstant('AUTO_INCREMENT') : null;
        $tableSchema->attributeDefaults = $attributes;

        Singleton::set($className, $tableSchema);
        return $this->_tableSchema = $tableSchema;
    }
}
